# 控制器中实现按需加载组件

# 背景

在控制器的项目中,一般页面用到了哪些组件,我们会在对应的deps依赖文件中添加上组件名,这样可以保证在页面初始化前能正确获取到所有的组件。

但是这么做的坏处就是不管这些组件需不需要初始化时用到都会被加载,极大地影响了页面加载速度,浪费了带宽,于是实现了按需加载组件的功能。

# 实现

WLAN/js/components/CommonMethod.js中定义了个全局方法SF.asyncLoadModule

接收文件名(字符串或者数组)、回调、作用域、遮罩对象参数。

首先判断请求的文件被加载过没,这里控制器项目是把加载的文件挂载到全局的按路径命名的变量上,所以可以通过一个循环判断该变量是否存在

/**
 * 判断是否存在该命名空间的文件
 * @param path
 * @returns {boolean}
 */
SF.isExistNameSpace = function (path) {
    var pathArr = path.split('.');
    var res = window;
    for (var i = 0; i < pathArr.length; i++) {
        if (res[pathArr[i]]) {
            res = res[pathArr[i]];
        } else {
            return false;
        }
    }
    return true;
};

如果存在需要加载的文件,则调用以前SF框架的SF.core.loadModule,去走catjs那一套加载需要的文件。

这里对loadModule做了点小修改,以前是针对页面做的,加载页面就去取对应的依赖项,递归出来然后catjs去向php请求,所以不支持同时传入多个文件名。

由于存在不同文件可能依赖有重复的文件,同时去调用SF.asyncLoadModule会起不到去重作用,所以将SF.core.loadModule改为支持数组形式。

具体修改在WLAN/js/override/framework/module.js

SF.core.loadModule成功后执行传入的回调。

有传入遮罩对象的,就关掉遮罩

如果不存在需要加载的文件,则直接走回调。

贴下源码

/**
 * 执行异步加载
 * @param moduleName
 * @param successCallback
 * @param scope
 * @param loadingTarget
 */
SF.asyncLoadModule = function (moduleName, successCallback, scope, loadingTarget) {
    if (typeof moduleName === 'string') {
        moduleName = [moduleName];
    }
    if (loadingTarget && loadingTarget.el) {
        loadingTarget = loadingTarget.el;
    }
    
    var needLoadModule = [];
    moduleName.forEach(function (module) {
        if (!SF.isExistNameSpace(module)) {
            needLoadModule.push(module);
        }
    });
    var cb = function () {
        if (loadingTarget) {
            loadingTarget.unmask();
        }
        successCallback && successCallback();
    };
    
    if (needLoadModule.length) {
        if (loadingTarget) {
            loadingTarget.mask(_('正在请求数据,请稍候……'), 'x-mask-loading');
        }
        SF.core.loadModule(needLoadModule, function (success) {
            if(success){
                cb();
            }else{
                SF.alert(_("加载页面出错,请检查网络连接是否正常。"));
                SF.error(_("加载页面[{0}]出错!", needLoadModule));
            }
        }, scope);
    } else {
        cb();
    }
};

# 使用

  • 去掉页面首次渲染不需要的文件依赖

  • 通常在触发mgr的action之前,去加载所需的文件

    各种组件的事件触发时:

    handler:function(){
        SF.asyncLoadModule(
            'B.wireless.NewAP.Form',
            function () {
                mgr.executeAction(
                    {
                        isTopoPage: true,
                        pageMgr: mgr,
                        grid: {getSelections: getSelections},
                        rs: gridData
                    },
                    "modifyAp"
                );
            },
            mgr,
            Ext.getBody()
        );
    }
    

    直接写mgr中:

    'showDrawerForm.end': {
        fn: function (act, mgr) {
            SF.asyncLoadModule(
                'B.equipment.IntelligentNetworkTopos.NodeDetailForm',
                function () {
                    var nodeDetailWindow = mgr.panel.nodeDetailWindow;
                    nodeDetailWindow.delayInitItem();
                    nodeDetailWindow.setTitle(mgr.actItem.name);
                    nodeDetailWindow.showWindow(mgr.showWindowData);
                },
                mgr,
                mgr.panel
            );
        }
    },
    
  • 遮罩看需求,Ext.getBody()可获取到根元素,适用与在页面的弹窗再显示弹窗的场景,直接在页面调用的可传入页面的el。

# 效果

使用前:

521个请求,耗时15.17s

使用后:

187个请求,耗时6.17s

页面加载速度提升了2.5倍左右,肉眼可见的快了。

# 弊端

按需加载,加快了页面加载的速度,但在请求额外组件时,耗时就比以前大了。

不过一般来说组件不会存在太多依赖,所以加载速度还是可以的,也只有第一次加载该组件会慢,后面都会使用缓存好的。